Parity 科技公司研发的 Substrate 框架,让我们拥有了可以几乎不受限制的快速的开发出一个完整、高性能、安全的区块链项目。今年6月份,一块链习与 Polkadot 社区大使陈锡亮老师倾力打造了《Substrate快速入门与实战开发》课程。目前第三期课程已经进行到第五周,同学们平常会在班级群将学习中遇到的难题或不解向老师、助教提问、讨论。另外,每周六晚 8 点,都会进行《Substrate 快速入门与开发实战》开发课的内容知识拓展——作业点评会,助教们会从解题思路、以及本组学员的作业问题分析每一课的知识点。现在将第四周班级群日常优质的讨论内容以及张静波、黄志光助教对第 4、5、6课的作业点评分享给大家。Q:KuoYeh@在读博士:有没有人知道作业test的Currency,Randomness怎么写啊?我Rust OOP还没时间细看。https://github.com/paritytech/substrate/blob/master/frame/utility/src/lib.rs#L714A:张静波@助教:把这三部分,加进去,然后修改一下编译错误,就差不多了。缺 `Event`, `Currency`, `Randomness` ,就先加到 impl Trait for Test 里。Q:村上香菜子@电商:但是按照刚才说的步骤,还是不过。A:Ratentlan@程序员:这时候, 他们还没从重构完成,你也可以改代码。就是你代码里面跟toml保持一致就行了。toml叫balances, 你就别写pallet-balances。Q:alan poon@社群成员:怎么用offchain worker download 一链的block header?A:陈锡亮@讲师:offchain 可以读链上storage,system模块里面应该有存最近几个区块的数据。Q:alan poon@社群成员:但是我是要download all the block headers, 答案还是一样吗?A:陈锡亮@讲师:offchain worker是每个block都会执行的,所以每个block存当前的header就好了。Q:建怀@社群成员:offchain worker里面保存数据会不会所有节点也会同步?Q:建怀@社群成员:要维护全网状态一致,还要走链上的操作才行。一个节点通过offchain worker 喂一个状态给链上,比如价格,如何达成全网共识呢?A:陈锡亮@讲师:和预言机原理一样,要么POA,要么POS,甚至有POW的。Q:建怀@社群成员:需要有一个机制来确认链下数据真实性。A:陈锡亮@讲师:主要就是经济模型,提高作恶成本。Q:建怀@社群成员:授权出块节点跑offchain worker,他们能直接操作链上状态,然后直接就是共识,这样是否可行。当然只是有限的不是核心的链上状态。A:陈锡亮@讲师:可以,那就是POS的方法,而且复用了出块的POS,会简单很多。Q:建怀@社群成员:offchain worker用来做其他公链交易真实性的验证,但没必要跑太多重复的验证,这个逻辑在思考。验证一个交易,希望能直接更改一下这个交易状态,其他的worker就不要做重复的工作了。但这个更改状态,要能快速达成共识,直接POA授权出块节点干好了。A:陈锡亮@讲师:嗯,可以交给验证人节点就最简单了。老师在视频里提到,这里边真实的price是balance类型。那上面框的这话,是不是把Option封装的balance类型的价格取出来了,重新赋值给到price这个变量呢?所以这个price变量就从原来的Option类型,现在变成Balance类型啦?是不是可以改写成下面的形式?if let Some(balance_price) = price{......}这样就把Option类型的price里边的值,取出来放在balance_price里?这里的情况也是类似的,看着像同一个变量,从原来的Some类型变成了某个具体的类型。A:黎倚杭@助教:不是类型变换,是从可空的类型中,取出非空的对象。Q:村上香菜子@电商:但是变量前面等于Some(a),后面等于a,两种类型啊。https://kaisery.github.io/trpl-zh-cn/ch06-03-if-let.htmlQ:村上香菜子@电商:老师有提到T在某个地方,被限制了使用类型,一般是在哪里做限制的呢?是在写这个函数的Module声明里做的这个T:Trait这个限制吗?所以在这整个Module里,T都是指Trait对吗?前面这个Module限制了T: Trait, Trait的定义就是后面这张图,对吗?A:Ratentlan@程序员:对,T是个变量, 叫什么名字都可以看到T并不能认为是 T:Trait, 可能是 T:Index. 要进一步看。Q:村上香菜子@电商:在Substrate里边,每一个crate文件,是不是必须定义一个名字叫做Trait的trait,一个名字叫做Module的struct,用来封装整个模版?这就是它们的模版一样的东西,这样的话,在这个新建的crate里边,Trait就负责把要用到的其它crate里边要继承的东西都继承过来。Module就用来承接Trait定义的东西,然后把函数都写到Module里边,是这样吗?A:陈锡亮@讲师:嗯没错。理论上这些名字可以改的只要保持一致就好,不过所有人都这么写。Q:Ratentlan@程序员:有个问题, 是否收到kitties中定义的事件的时候, 就是这个事件的块已经finalized的时候?A:陈锡亮@讲师:一般情况下出块确认了就会收到事件,finalize 是有可能一次 finalize多个区块的,但出块确认就是一个一个的。Q:Ratentlan@程序员:是否最保险的方法就是扫描每个finalized的块, 然后看里面的自己需要的事件. 不然光订阅可能会因为网络问题漏掉事件。想做的事情就是, 根据finalized的交易, 在中心化服务做点事情。在js中用 api.query.system.events((events) => { 监听的事件可能是在分叉上的. 有什么简单的办法达到只监听确定下的事件, 并且还不漏块吗?A:陈锡亮@讲师: 可以存着最后扫描的区块,监听finalized的区块,扫描每个从最后扫描的区块到最新finalized区块到事件。没有直接简单的方法,不过按照上面的思路也不会太复杂。这句话好复杂,能否麻烦帮忙拆解一下?等号前面的T跟等号后面的2个T是同一个T吗?这个能不能写成 A:陈锡亮@讲师:是同一个T,这行里面没法有Self。Q:村上香菜子@电商:那等号左边的T的trait bound是哪里定义的呢?这句话没有放在 Trait的定义里,是单独放出来的呀。A:陈锡亮@讲师:就是右边的T as Trait。Q:村上香菜子@电商:不能用Self的原因,是不是就是因为这句话没有定义在Trait的{}里?所以不会有实例实现?如果是的话,为什么不把它写进去Trait的定义里呢?这句话能直接写成下面的句子吗?type BalanceOf<T> = <Trait::Currency as Currency<system::Trait::AccountId>>::Balance;这个BalanceOf的功能,我可不可以直接用 Trait::Currency::Balance ?做测试的时候, exist()这里出了问题,请老师指点一下这个语法错在哪里?A:陈锡亮@讲师:可以分开拆成几步再试试。不过这个直接用 ownedkitties<t>::exists就好了。Storage是LinkedList的泛型参数,外部是不能调用的。Q:村上香菜子@电商:改成ownedkitties,可以了。这相当于调用不了OwnedKittiesList来操作自己的map,直接用自己的map定义啊。A:陈锡亮@讲师:嗯。不然的话也没太多意义,LinkedItem<OwnedKitties>::Storage明显比直接OwnedKitties麻烦。Q:村上香菜子@电商:kitty_price()是decl_storage里边定义的KittyPrices的get方法。为什么调用它的时候,不用Self::KittyPrices::kitty_price(),而是直接Self::kitty_price()呢?A:陈锡亮@讲师:这边的Self是Module,decl_storage是把方法实现在Module里面的。KittyPrice已经有了get读数据。只是一个方便点的getter,不然的话是<KittyPrice<T>>::get()。Q:村上香菜子@电商:所以这个 kitty_price是定义在Module里的单独的方法,不是KittyPrices的成员,所以不需要用KittyPrices作为前缀。Q: 村上香菜子@电商:这个是什么语法呢?为什么泛型参数在::后面?不是应该紧跟名字的吗?A:Ratentlan@程序员:好像叫 turbofish。Q:村上香菜子@电商:另外,像这个测试的 Trait 实现的这些赋值方法,老师有没有什么参考文档或者帮助信息啊?我完全一头懵,全是按照助教给的文档复制粘贴的。感觉里边的关系错综复杂。A:陈锡亮@讲师:这些都是kitties模块中的Trait里面限定的。Q:KuoYeh@在读博士:请问再测试时若是用不到,Event,Currency,Randomness, 我该怎么写好?A:陈锡亮@讲师:有两个方法,一个是自己建一个struct,impl 对应的trait,传进去,一个是引入实现了这个trait的模块,传进去。可以参考substrate 里面各种模块比如staking, treasury等的mock.rs文件。Q:KuoYeh@在读博士:有没有substrate unit test的一些范例?https://github.com/paritytech/substrate/blob/master/frame/staking/src/mock.rshttps://github.com/paritytech/substrate/blob/master/frame/treasury/src/lib.rs#L366substrate里面每个模块都有测试,都可以参考。在重构create函数的时候,对kitty_id的溢出做了代码优化,但create和insert_kitty函数有重复代码,还有优化空间;combine_dna写了伪代码的实现,但是有逻辑问题,可以在讲了测试方法后进行测试和修正;create函数重构比较好,combine_dna功能实现,但还可以进行位运算优化。作业质量高,优化了combine_dna,并代码实现了transfer功能。这是郝明 同学的实现,大家可以学习参考一下,进行改进。参考workshop对create函数做了优化,但功能不完整,希望能多看一下视频,参考一下其它同学的作业,继续加油。实现了do_transfer,判断了kitty_id合法性,但没有判断只有主人才有权限调用;朱强和郝明的作业质量都比较高,郝明代码实现了交易功能功能,很赞。这个是 朱强 同学的实现,可以参考一下,xvhehui同学参考了workshop,但代码不完整,希望多看看视频,继续加油。交了作业的同学,作业完成度都比较高,并且有对下次作业的思考设计,这点很值得表扬。没及时交作业的同学,也希望能继续努力,跟上课程。其实可以发现,老师的作业代码量真的很少,希望大家能再接再厉,继续加油。第六课我们把之前实现在Storage Item OwnedKitties上的链表方法抽象成独立的模块,让链表模块更加通用。本组共有1位同学提交了第六课作业,完成质量很好,我们一起来看一下这位同学的作业,顺便复习一下链表数据结构。首先是append方法,append方法用于插入一个元素到链表尾部,我们课程中的链表头元素有prev/next指向上一个和下一个元素,是一个双向链表。1) 找到要插入的位置,以及该位置的上一个和下一个元素2) 把上一个元素的next指向自己,把下一个元素的prev指向自己3) 把自己的prev指向上一个元素,把自己的next指向下一个元素稍有不同:我们课程中实现的链表头的prev指向了链表尾(便于找到链表尾进行append操作),但链表尾的next并不需要指向链表头。我们组这位同学的作业中,append方法如下,我做了点注释方便大家理解。然后是remove方法,remove方法用于移除链表中任意位置(链表头除外)的元素。1) 找到要删除的位置,以及该位置的上一个和下一个元素。我们组这位同学的作业中,remove方法如下,我做了点注释方便大家理解。方法逻辑正确,代码中先读出数据,操作链表后,再调用StorageMap提供的remove方法删除数据,这里可以做一点小优化。读取和删除确保都可以完成的情况下(中途不需要ensure!、不会panic、不会return等),可以使用StorageMap提供的take方法一步完成。最后是修复测试,需要在保证测试用例正确性的前提下,用测试来驱动开发。这位同学的作业中,正确修复了测试,并且执行cargo test相关命令也能通过,这里就不展开点评。 利用各种数据结构提升Substrate Storage item的存取能力是一个非常好的课题,课程中的linked_item是很典型的例子,值得深入了解。